home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Shareware Grab Bag
/
Shareware Grab Bag.iso
/
050
/
flush12.arc
/
FLUSH12.ACC
Wrap
Text File
|
1986-11-28
|
13KB
|
283 lines
*******************************************************************************
FLUSH.ACC
Version 1.2
January 29, 1986
by Randy Forgaard
CompuServe 70307,521
This file, FLUSH.ACC, is related to the file FLUSH.PAS in DL 1 of the Borland
SIG on CompuServe, but FLUSH.PAS is not needed in order to use the routines
below.
This file is for use with the Turbo Access portion of the MS-DOS Turbo
(Database) Toolbox version 1.1, together with MS-DOS or PC-DOS Turbo Pascal
3.01A or higher (regular, 8087, or BCD version). If you have version 1.0 of
the Toolbox, see the file TBXFIX in DL 1 of the Borland SIG for the source code
changes that will upgrade your copy to version 1.1. If you have version 3.00A
or 3.00B of Turbo Pascal, arrange with Borland Customer Service to upgrade your
copy of Turbo to 3.01A or higher (3.00A and 3.00B are not compatible with Turbo
Database Toolbox). The routines below will not work with earlier (pre-3.0)
versions of Turbo Pascal, nor with Turbo Pascal for operating systems other
than DOS.
This file describes certain changes that you can make to the source code of
Turbo Access to make your database virtually impervious to system crashes.
Normally, when you use Turbo Access and you use MakeFile, AddRec, PutRec,
DeleteRec, MakeIndex, AddKey, or DeleteKey to create or change a DataFile or
IndexFile, not all of the changes you make are necessarily written to the disk
right away. Typically, the most recent changes are kept in memory buffers by
Turbo Access, the Turbo run-time system, and/or DOS. This technique allows
file output to proceed more quickly, since an entire index page or disk sector
can be written to the disk at once. When a file gets closed, its memory
buffers are all written to disk. However, if the system should crash (due to
program error, accidental Ctrl-Alt-Del, power failure, tripping over the plug,
etc.) after a file has been changed and before it gets closed, the recent
changes that are in memory but not on the disk will not get written. Even more
insidious, a system crash can mean that the file length in the directory entry,
or the record numbers in IndexFiles, do not get written to disk, rendering the
information in the database inconsistent and probably unusable.
One can guard against system crashes by flushing all information in a file's
memory buffers out to disk, every time that the contents of that file changes.
Unfortunately, using the routines provided by Turbo Access, a DataFile or
IndexFile can only be flushed by closing and reopening the file. This is
primarily because DOS provides no explicitly documented way to flush a file's
buffers without closing/reopening. Closing and reopening a file every time its
contents change is extraordinarily expensive at run-time, largely because DOS
must perform a directory lookup every time a file is reopened.
Fortunately, there actually IS a little-known method for flushing a file's
buffers and updating the file length in the file's directory entry under DOS,
without reopening the file. The technique for "flushing" a file is: 1) invoke
DOS function 45H, "Duplicate a File Handle (DUP)," to duplicate the file
handle, and then 2) invoke DOS function 3EH, "Close a File Handle" to close the
extra file handle that you just created (this action does not close the
original file handle). The "Close" function 3EH flushes the file's buffers, as
documented in the DOS Technical Reference manual. Yet the original file handle
is still valid and usable, so the file does not need to be reopened. Thanks
for this clever technique go to Dan Daetwyler in a letter to Ray Duncan's
16-Bit Software Toolbox column in the December '85 issue of Dr. Dobb's Journal.
One of the routines below is a new routine for use with Turbo Access, called
FlushFile. This procedure will perform an actual flush on any DataFile, so
that the DataFile will be completely up-to-date and consistent even if the
system should crash after the flush. The FlushFile routine uses the technique
described above. Note: The FLUSH.PAS file, mentioned above, contains two
routines for flushing standard Turbo files (rather than Turbo Access files).
The FlushAny procedure in FLUSH.PAS is very similar to the FlushFile procedure
below. The differences are that FlushFile updates the internal Record 0
maintained by Turbo Access, and that FlushFile uses TaIOcheck, the internal
Turbo Access routine, to report errors, rather than writing the errors directly
to the primary output.
Just as FlushFile will flush any DataFile, FlushIndex (the other procedure
below) will flush any IndexFile. The FlushIndex code is similar to the
CloseIndex routine included with Turbo Access, with the following differences:
the index pages are not removed from the page stack after they are written to
disk (which can make subsequent index searches proceed faster), and the final
call to CloseFile is replaced by a call to FlushFile.
After making the Turbo Access source code changes indicated below, FlushFile
and FlushIndex may be called explicitly by the calling program whenever
desired. Alternatively, the calling program may set the global Boolean
variable FailSafe to "true," and the (modified) Turbo Access routines will
automatically invoke FlushFile or FlushIndex (depending on context) whenever a
DataFile or IndexFile is changed. By default, FailSafe is "false." If you
decide to set FailSafe to "true," it should be done immediately after the call
to InitIndex at the beginning of your main program; i.e.:
...
InitIndex;
FailSafe := true;
...
With the changes below, the only possibility that data can become corrupted due
to a system crash is if the crash occurs between when the file gets changed and
when FlushFile or FlushIndex is automatically (or explicitly) invoked
immediately afterward. That window of vulnerability is a small number of
microseconds, and occurs only at the actual moment that the program outputs a
new data record or index key. If your program is reading records, searching
the database, waiting for user input, computing, printing, updating the screen,
etc., your database is still safe if the system crashes. However, if there
should be a power failure at the exact moment that data is being written, it is
likely that the failure will also cause serious problems on the disk as a
whole, trashing the FAT or some directories. Thus, in this very unlikely
event, the disk may need to be restored from backup tape or disks in any case,
and you probably shouldn't lose sleep over this possibility.
There are two important DISadvantages to using the Turbo Access code changes
below, assuming you have set FailSafe to "true":
1) MakeFile, AddRec, PutRec, DeleteRec, MakeIndex, AddKey, and DeleteKey
will all execute somewhat slower, because of the automatic file flush. This
delay will not be nearly as great as closing/reopening the file, but it can be
noticeable if done frequently. If your database application spends a
significant amount of time updating records, you might want to test how much
slower your application runs with FailSafe = "true" than with FailSafe =
"false." You can reduce the delay by living a little dangerously and running
with FailSafe = "false," invoking FlushFile and FlushIndex explicitly at
strategic points.
2) The file flushing code below only works with DOS. If you plan to port
your application to other operating systems later, you may want to build a more
robust scheme for detecting and recovering from system crashes, since you won't
be able take advantage of the crash protection below.
It would be wonderful if Turbo Access could be made to automatically flush file
buffers under other operating systems, in addition to DOS. If you find a way
to do this, PLEASE PLEASE add this information to the FLUSH.ACC file
accordingly, and re-upload to DL 1 of the Borland SIG! For that matter, if you
are feeling truly altruistic, you might want to make similar additions to
FLUSH.PAS, too, showing how to flush normal Turbo files under other operating
systems.
Many thinks to Rick Amerson (CompuServe 72477,1566) for his generous help in
testing the code below, to Andy Miller (CompuServe 70357,3656) for pointing out
that the routines below do not use any undocumented features of DOS function
calls, and to Peter Thomas (CompuServe 75716,2377) for suggesting the
efficiency improvement to FlushIndex.
Change Log:
Version 1.1: Removed caveats about undocumented use of DOS functions, since
this file actually only uses documented features of DOS.
Version 1.2: Slight changes to FlushIndex so that index pages do not get
removed from the page stack in memory when they get flushed to
disk. Can make subsequent index searches faster, since those
index pages will not need to be re-read from disk.
*******************************************************************************
The code changes to add "flush" capability to MS-DOS Turbo Access version 1.1
are shown below. Please BE SURE to make a backup copy of your Turbo Access
source code before making these changes. Note: When making the changes below,
be sure to use ACCESS3.BOX, not ACCESS.BOX, since ACCESS.BOX is only for use
with Turbo 2.0, and the changes below can only be used with Turbo 3.01A and
higher.
STEP 1: Add the following declaration to ACCESS3.BOX, immediately prior to
TaIOcheck:
const
FailSafe: Boolean = false;
STEP 2: While you're at it, fix the following minor bug (unrelated to flushing
file buffers) that may be in your version of ACCESS3.BOX: In procedure
TaIOcheck, change the statement "I := 0;" to be "I := 1;". Borland has
been alerted about this bug.
STEP 3: Insert the following routine immediately after the routine TaIOcheck in
ACCESS3.BOX:
{Flushes the buffers associated with the DataFile "DatF," and updates the file
length in the directory entry of "DatF," without closing "DatF."}
procedure FlushFile (var DatF: DataFile);
var
handle: Integer absolute DatF; {File handle is the first word of a DataFile}
regs: record
case Integer of
1: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: Integer);
2: (AL, AH, BL, BH, CL, CH, DL, DH: Byte)
end;
begin
{Code from beginning of CloseFile}
DatF.Int2 := DatF.NumRec;
Move(DatF.FirstFree, TaRecBuf, 8);
{Simulate a PutRec(DatF, 0, TaRecBuf) (to avoid FlushFile/PutRec recursion)}
Seek(DatF.F, 0);
BlockWrite(DatF.F, TaRecBuf, 1);
IOstatus := IOresult;
TaIOcheck(DatF, 0);
{Flush DatF}
IOstatus := $F0; {"Disk write error" I/O error...just in case}
regs.AH := $45; {DOS function to duplicate a file handle}
regs.BX := handle;
MsDos(regs);
if Odd(regs.Flags) then {Check if carry flag is set}
TaIOcheck(DatF, 0);
regs.BX := regs.AX; {Put new file handle into BX}
regs.AH := $3E; {Dos function to close a file handle}
MsDos(regs);
if Odd(regs.Flags) then {Check if carry flag is set}
TaIOcheck(DatF, 0)
end {FlushFile};
STEP 4: Insert the following routine immediately after the routine OpenIndex in
ACCESS3.BOX:
{Flushes the buffers associated with the IndexFile "IdxF," and updates the file
length in the directory entry of "IdxF," without closing "IdxF."}
procedure FlushIndex (var IdxF: IndexFile);
var
I: Integer;
begin
{Similar to CloseIndex:}
for I := 1 to PageStackSize do
with TaPageStk[I] do
if (IndexFPtr = Addr(IdxF)) and Updated then
begin
TaPack(Page,IdxF.KeyL);
{Simulate a PutRec(IdxF.DataF, PageRef, Page) (to avoid redundant
FlushFile calls)}
Seek(IdxF.DataF.F, PageRef);
BlockWrite(IdxF.DataF.F, Page, 1);
IOstatus := IOresult;
TaIOcheck(IdxF.DataF, PageRef);
TaUnpack(Page,IdxF.KeyL);
Updated := false
end;
IdxF.DataF.Int1 := IdxF.RR;
FlushFile(IdxF.DataF)
end {FlushIndex};
STEP 5: Also in ACCESS3.BOX, add the following line just before the "end" at
the end of the PutRec routine:
if FailSafe then FlushFile(DatF);
STEP 6: Insert the following line just before the very last "end" statement at
the very end of the file ADDKEY.BOX:
if FailSafe then FlushIndex(IdxF);
STEP 7: Also, insert the above line just before the very last "end" statement
at the very end of the file DELKEY.BOX.
STEP 8: Don't forget to add the line "FailSafe := true;", if desired, to your
main program, immediately after the call to InitIndex.
That's it! If you have any questions, comments, or trouble with the above
changes, please feel free to contact me on the Borland SIG or via EasyPlex on
CompuServe. I sincerely hope the above information is useful to you.
-- Randy Forgaard